ANIMA.add("anima.composition", function(A) {

A.namespace("anima.composition");

A.anima.composition.create = function(prepareFn, runFn, componentFactory) {
   return new A.anima.composition.Composition(prepareFn, runFn, componentFactory);
};

A.anima.composition.Composition = function(prepareFn, runFn, componentFactory) {
   if (componentFactory && componentFactory != null)
      this.componentFactory = componentFactory;
   else
      this.componentFactory = new A.anima.component.jslocal.ComponentFactory();
   
   this.components = [];
   this.dependencies = [];
   
   this.yuiSpace = ANIMA.use();

   this.prepareFn = prepareFn;

   this.runFn = runFn;
   
   this.schedule = [];
   this.finishScheduling = false;
};

A.anima.composition.Composition.prototype = {
   
   component: function(instanceid) {
      return this.components[instanceid];
   },
   
   /*
    * scheduleType
    *   1 - createInstance
    *   2 - connect
    *   3 - finish preparation
    *   
    * status
    *   0 - waiting
    *   1 - processing
    */
   putSchedule: function(scheduleType, args) {
      this.schedule.push({scheduleType: scheduleType, args: args, status: 0});
   },
   
   /*
    * Checks whether there is still a processing task (status = 1),
    * if not:
    *   a) gets the next waiting task (status = 0) and start processing;
    *   b) if there is no waiting task, calls back the composition run function
    */
   checkSchedule: function() {
      var submit = -1;
      for (var s = 0; s < this.schedule.length && submit === -1; s++)
         if (this.schedule[s].status === 1)
            submit = -2;
         else
            submit = s;

      if (submit >= 0) {
         this.schedule[submit].status = 1;
         var args = this.schedule[submit].args;
         switch (this.schedule[submit].scheduleType) {
            case 1 : this.componentFactory.createInstance(args[0], args[1], this);
                     // this task will be closed by the componentFactory
                     break;
            case 2 : this.components[args[0]].connect(this.components[args[1]]);
                     this.closeScheduleTask();
         }
            
      } else if (submit === -1 && this.finishScheduling) {
         // there is neither waiting nor processing tasks, and there is no more scheduling
         this.yuiSpace.animaCompositionArgs = {composition: this};
         this.yuiSpace.use(this.dependencies, this.runWrapper);
      }
   },
   
   /*
    * Closes the processing task (status = 1) by removing it from the schedule.
    */
   closeScheduleTask: function() {
      var close = -1;
      for (var s = 0; s < this.schedule.length && close === -1; s++)
         if (this.schedule[s].status === 1)
            close = s;
      if (close >= 0)
         this.schedule.splice(close, 1);
      this.checkSchedule();
   },
   
   createInstance: function(webid, instanceid) {
      this.scheduleCreateInstance(webid, instanceid);
      this.checkSchedule();
   },
   
   scheduleCreateInstance: function(webid, instanceid) {
      this.putSchedule(1, [webid, instanceid]);
   },
   
   connect: function(instanceidRequires, instanceidProvides) {
      this.scheduleConnect(instanceidRequires, instanceidProvides);
      this.checkSchedule();
   },
   
   scheduleConnect: function(instanceidRequires, instanceidProvides) {
      this.putSchedule(2, [instanceidRequires, instanceidProvides]);
   },

   addComponent: function(instanceid, component) {
      this.components[instanceid] = component;
   },
   
   addDependency: function(webid) {
      this.dependencies.push(webid);
   },
   
   run: function() {
      // var yuiRun = this.yuiSpace.use();
      
      this.prepareFn(this);
      this.finishScheduling = true;
      this.checkSchedule();
      
      // yuiRun.animaCompositionArgs = {composition: this};
      // yuiRun.use(this.dependencies, this.runWrapper);
   },
   
   runWrapper: function(A) {
      if (A.animaCompositionArgs.composition.runFn != null)
         A.animaCompositionArgs.composition.runFn(
               A.animaCompositionArgs.composition, A);
   }

};

});